﻿namespace RESX_Utils
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Resources;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.XPath;

    /// <summary>
    /// This class contains the processing logic for the utility functions.
    /// </summary>
    internal class ResourceUtilities
    {
        /// <summary>
        /// Calls CompareResources asynchronously.
        /// </summary>
        /// <param name="resourceFile">The full path to the resource file to compare.</param>
        /// <param name="resourceFile2">The full path to the resource file to be compared to.</param>
        /// <param name="log">The builder for the output results.</param>
        /// <returns>The processing result message.</returns>
        public async Task<string> CompareResources_Async(string resourceFile, string resourceFile2, StringBuilder log)
        {
            var utilityTask = Task<string>.Factory.StartNew(() => this.CompareResources(
                resourceFile,
                resourceFile2,
                log));

            await utilityTask;

            return utilityTask.Result;
        }

        public async Task<string> AnalyzeUssage_Async(string resourceFile, IEnumerable<string> projectFiles, List<string> extentions, List<string> exclusions, StringBuilder log)
        {
            var utilityTask = Task<string>.Factory.StartNew(() => this.AnalyzeUssage(
                resourceFile,
                projectFiles,
                extentions,
                exclusions,
                log));

            await utilityTask;

            return utilityTask.Result;
        }

        public async Task<string> DetectDuplicates_Async(string resourceFile, StringBuilder log)
        {
            var utilityTask = Task<string>.Factory.StartNew(() => this.DetectDuplicates(resourceFile, log));

            await utilityTask;

            return utilityTask.Result;
        }

        public string DetectDuplicates(string resourceFile, StringBuilder log)
        {
            // This is to allow the resource reader to properly load references.
            var currentDirectory = Environment.CurrentDirectory;
            ulong duplicates = 0;
            Environment.CurrentDirectory = System.IO.Path.GetDirectoryName(resourceFile);

            var resources = new ResXResourceReader(resourceFile);

            var dictionary = new Dictionary<string, string>();
            log.Append("Duplicated String,Resource 1,Resource 2\r\n");

            foreach (DictionaryEntry entry in resources)
            {
                if (entry.Value.GetType() == typeof(string))
                {
                    if (dictionary.ContainsKey(entry.Value.ToString()))
                    {
                        duplicates++;
                        log.AppendFormat("\"{0}\",{1},{2}\r\n", entry.Value.ToString().Replace("\"", "\"\""), dictionary[entry.Value.ToString()], entry.Key.ToString());
                    }
                    else
                    {
                        dictionary.Add(entry.Value.ToString(), entry.Key.ToString());
                    }
                }
            }

            Environment.CurrentDirectory = currentDirectory;

            var message = string.Format("{0} duplicated resources were pasted to Clipboard.", duplicates);
            return message;
        }

        public string AnalyzeUssage(string resourceFile, IEnumerable<string> projectFiles, List<string> extentions, List<string> exclusions, StringBuilder log)
        {
            var resources = this.LoadStringResources(resourceFile);
            var sourceCode = this.LoadSourceCode(projectFiles, extentions);

            var unusedResources = new List<string>();
            var excludedResources = new List<string>();

            foreach (var resource in resources.Keys)
            {
                // Check for exclusions.
                var isExclusion = false;

                foreach (var exclusion in exclusions)
                {
                    if (resource.StartsWith(exclusion))
                    {
                        excludedResources.Add(string.Format("{0} (pattern: {1})", resource, exclusion));
                        isExclusion = true;
                        break;
                    }
                }

                if (isExclusion)
                {
                    continue;
                }

                if (!sourceCode.Contains(resource))
                {
                    if (resource.Contains("_"))
                    {
                        // Looking for a constant that includes the resource id up to the last "_" symbol.
                        var concatPattern = "\"" + resource.Substring(0, resource.LastIndexOf("_")) + "_\"";
                        if (sourceCode.Contains(concatPattern))
                        {
                            excludedResources.Add(string.Format("{0} (pattern: {1})", resource, concatPattern));
                            continue;
                        }

                        concatPattern = "\"" + resource.Substring(0, resource.LastIndexOf("_")) + "_{0}\"";
                        if (sourceCode.Contains(concatPattern))
                        {
                            excludedResources.Add(string.Format("{0} (pattern: {1})", resource, concatPattern));
                            continue;
                        }
                    }

                    unusedResources.Add(resource);
                }
            }

            log.Append("Unused Resources" + Environment.NewLine + Environment.NewLine);
            foreach (var resource in unusedResources)
            {
                log.Append(resource + Environment.NewLine);
            }

            log.Append(Environment.NewLine + "Excluded Resources" + Environment.NewLine + Environment.NewLine);
            foreach (var resource in excludedResources)
            {
                log.Append(resource + Environment.NewLine);
            }

            var message = string.Format("{0} unused, {1} exceptions of {2} total resources were pasted to Clipboard.", unusedResources.Count, excludedResources.Count, resources.Count);

            if (unusedResources.Count > 0)
            {
                var newFileName = this.AppendToFileName(resourceFile, ".unusedremoved");

                this.RemoveResources(resourceFile, newFileName, unusedResources);

                message = message + Environment.NewLine + Environment.NewLine + string.Format("Resource file with unused resources removed was saved as {0}", System.IO.Path.GetFileName(newFileName));
            }

            return message;
        }

        public string CompareResources(string resourceFile, string resourceFile2, StringBuilder log)
        {
            var resources = this.LoadStringResources(resourceFile);
            var resources2 = this.LoadStringResources(resourceFile2);
            var duplicatedResources = new List<string>();
            var mishmatchingResources = new Dictionary<string, KeyValuePair<string, string>>();

            foreach (var resource in resources)
            {
                if (resources2.ContainsKey(resource.Key))
                {
                    duplicatedResources.Add(resource.Key);
                    if (resources2[resource.Key] != resource.Value)
                    {
                        mishmatchingResources.Add(resource.Key, new KeyValuePair<string, string>(resource.Value, resources2[resource.Key]));
                    }
                }
            }

            log.AppendFormat("Resource Id,{0},{1}\r\n", resourceFile, resourceFile2);

            foreach (var resource in mishmatchingResources)
            {
                log.AppendFormat("{0},\"{1}\",\"{2}\"\r\n", resource.Key, resource.Value.Key.Replace("\"", "\"\""), resource.Value.Value.Replace("\"", "\"\""));
            }

            var message = string.Format(
                "Analyzed {0} (resource file) and {1} resources (master resource file), found {2} matching resources and {3} discrepancies. Discrepancies were pasted to Clipboard.",
                resources.Count,
                resources2.Count,
                duplicatedResources.Count,
                mishmatchingResources.Count);

            if (duplicatedResources.Count > 0)
            {
                var newFileName = this.AppendToFileName(resourceFile, ".duplicatesremoved");
                this.RemoveResources(resourceFile, newFileName, duplicatedResources);
                message = message + Environment.NewLine + Environment.NewLine  + string.Format("Resource file with duplicated resources removed was saved as {0}", System.IO.Path.GetFileName(newFileName));
            }

            return message;
        }

        private string AppendToFileName(string fileName, string suffix)
        {
            return System.IO.Path.GetDirectoryName(fileName) + "\\" +
                    System.IO.Path.GetFileNameWithoutExtension(fileName) +
                    suffix +
                    System.IO.Path.GetExtension(fileName);
        }

        private Dictionary<string, string> LoadStringResources(string resXfile)
        {
            // This is to allow the resource reader to properly load references.
            var currentDirectory = Environment.CurrentDirectory;
            Environment.CurrentDirectory = System.IO.Path.GetDirectoryName(resXfile);

            var resources = new ResXResourceReader(resXfile);

            var dictionary = new Dictionary<string, string>();

            foreach (DictionaryEntry entry in resources)
            {
                if (entry.Value.GetType() == typeof(string))
                {
                    dictionary.Add(entry.Key.ToString(), entry.Value.ToString());
                }
            }

            Environment.CurrentDirectory = currentDirectory;
            return dictionary;
        }

        private string LoadSourceCode(IEnumerable<string> projectFiles, List<string> contentFileExtensions)
        {
            // Get all source code in a single string.
            var sourceCodeBuilder = new System.Text.StringBuilder();

            foreach (var projectFile in projectFiles)
            {
                var baseFolder = System.IO.Path.GetDirectoryName(projectFile) + "\\";
                var sourceFiles = this.GetProjectSourceCodeFiles(projectFile, contentFileExtensions);

                foreach (var sourceFile in sourceFiles)
                {
                    System.IO.TextReader textReader;
                    textReader = System.IO.File.OpenText(baseFolder + sourceFile);
                    sourceCodeBuilder.Append(textReader.ReadToEnd());
                    textReader.Close();
                }
            }

            return sourceCodeBuilder.ToString();
        }

        private List<string> GetProjectSourceCodeFiles(string projectFile, List<string> contentFilesExtenstions)
        {
            var projectFiles = new List<string>();

            var reader = new XmlTextReader(projectFile);
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (reader.Name.ToLowerInvariant() == "compile")
                        {
                            var file = this.GetIncludeAttribute(reader);
                            if (!string.IsNullOrEmpty(file))
                            {
                                projectFiles.Add(file);
                            }
                        }
                        else if (reader.Name.ToLowerInvariant() == "content")
                        {
                            // We need to analyze the cshtml files as well.
                            var file = GetIncludeAttribute(reader);
                            if (!string.IsNullOrEmpty(file))
                            {
                                foreach (var extension in contentFilesExtenstions)
                                {
                                    if (file.EndsWith(extension))
                                    {
                                        projectFiles.Add(file);
                                        break;
                                    }
                                }
                            }
                        }

                        break;
                }
            }

            return projectFiles;
        }

        private string GetIncludeAttribute(XmlTextReader reader)
        {
            if (reader.HasAttributes)
            {
                while (reader.MoveToNextAttribute())
                {
                    if (reader.Name.ToLowerInvariant() == "include")
                    {
                        return reader.Value;
                    }
                }
            }

            return string.Empty;
        }

        private void RemoveResources(string inputResXfile, string outputResXfile, List<string> resourceIds)
        {
            var resx = XElement.Load(inputResXfile);
            foreach (var id in resourceIds)
            {
                resx.XPathSelectElement("/data[@name=\"" + id + "\"]").Remove();
            }

            resx.Save(outputResXfile);
        }
    }
}
